package com.fm.financemanage.shiro;


import com.fm.financemanage.component.TokenComponent;
import com.fm.financemanage.constant.UserConstants;
import com.fm.financemanage.model.JWTToken;
import com.fm.financemanage.model.po.Accountant;
import com.fm.financemanage.model.po.SysUser;
import com.fm.financemanage.model.vo.SysUserVo;
import com.fm.financemanage.service.AccountantService;
import com.fm.financemanage.service.SysMenuService;
import com.fm.financemanage.service.SysRoleService;
import com.fm.financemanage.service.SysUserService;

import com.fm.financemanage.utils.MessageDigestUtil;
import com.fm.financemanage.utils.TextUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * 自定义Realm 处理登录 权限
 */
public class ShiroUserRealm extends AuthorizingRealm {

    @Autowired
    private SysMenuService menuService;

    @Autowired
    private SysRoleService roleService;

    @Autowired
    private SysUserService loginService;

    @Autowired
    private AccountantService accountantService;
    @Autowired
    TokenComponent tokenComponent;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 当访问到使用 需要使用角色或者权限方法的时候都会被调用
     * <p>
     * (如果使用缓存管理 那么只会被调用一次 后面就不会在调用 )
     * <p>
     * 在 AuthorizingRealm类的getAuthorizationInfo会调用当前方法
     * 并且通过 getAvailableAuthorizationCache 方法去获取用户在缓存管理中的信息
     * 如果返回信息为null,代表用户没有使用缓存管理器,所以会每次请求相关权限方法时都请求改方法获得权限数据
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {

        Object principal = SecurityUtils.getSubject().getPrincipal();
        SysUser user = null;
        if (!TextUtil.isEmpty(principal)) {
            user = new SysUser();
            BeanUtils.copyProperties(principal, user);
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if (user.getId() == 1L) {
            info.addRole("admin");
        } else {
            // 管理员拥有所有权限
            Set<String> roles = roleService.selectRoleKeys(user.getId());
            // 角色加入AuthorizationInfo认证对象
            info.setRoles(roles);
        }
        // 功能列表
        Set<String> menus = menuService.selectPermsByUserId(user.getId());

        // 权限加入AuthorizationInfo认证对象
        info.setStringPermissions(menus);

        return info;
    }

    /**
     * 登录认证
     * 调用场景
     * 1. 用户登录的时候调用 (这时候token中传的是 Principal)
     * 2. 当执行到授权的方法的时候调用 (这时候token中传的是 自定义属性 token)
     * 如果 是在用户登录的时候被调用,那么我们SimpleAuthenticationInfo中的凭证密码,是我们从数据库中获取的密码
     * 如果是在用户执行到授权被调用,那么我们SimpleAuthenticationInfo中的凭证密码是JWTToken中传入的token
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        try {
            JWTToken jwtToken = (JWTToken) token;
            SysUserVo user = null;
            if (!TextUtil.isEmpty(jwtToken.getToken())) {
                //在执行授权方法时被调用
                SysUserVo user1 = tokenComponent.getVoByToken(jwtToken.getToken());
                if (user1 != null) {
                    user = loginService.findByName(user1.getLoginName());
                    if (user == null) {
                        return null;
                    }
                    return new SimpleAuthenticationInfo(user, MessageDigestUtil.encryptMD5(jwtToken.getToken().getBytes()), getName());
                }
                return null;
            } else {
                //执行登录方法时被调用
                Object principal = jwtToken.getPrincipal();
                user = loginService.findByName(principal.toString());
                if (user == null) {
                    return null;
                }
                return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 清理指定用户授权信息缓存
     */
    public void clearCachedAuthorizationInfo(Object principal) {
        SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
        this.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 清理所有用户授权信息缓存
     */

    public void clearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            for (Object key : cache.keys()) {
                cache.remove(key);
            }
        }
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        Subject subject = SecurityUtils.getSubject();
        // 调用子类去清理缓存
        super.clearCache(subject.getPrincipals());
    }
}
